package student.singleton;

import java.util.concurrent.CountDownLatch;

/**
    懒汉式单例：延迟初始化
        1.私有化构造方法
        2.定义类变量，并不初始化
        3.使用方法讲变量暴露出去，并且在里面判断是否初始化过。
        缺点：非线程安全,延迟初始化，严格意义上不是不是单例模式

    注意：
        synchronized可以保证原子性、有序性和可见性。
        volatile却只能保证有序性和可见性。
        注意：这两个有序的意思是不一样的。
        synchronized 和 volatile的有序性比较：
        synchronized 的有序性：是持有相同锁的两个同步块只能串行的进入，即被加锁的内容要按照顺序被多个线程执行，但是其内部的同步代码还是会发生重排序，使块与块之间有序可见。
        volatile的有序性：是通过插入内存屏障来保证指令按照顺序执行。不会存在后面的指令跑到前面的指令之前来执行。是保证编译器优化的时候不会让指令乱序。
 */
public class LazySingleton {

    static CountDownLatch countDownLatch = new CountDownLatch(1);

    // 定义全局变量
    private static volatile LazySingleton lazySingleton;

    // 私有化构造函数
    private LazySingleton () { }

    public static LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            // 加上同步锁，保证线程安全，这里虽然使用了synchronized代码块，lazySingleton仍需要被volatile修饰
            synchronized(LazySingleton.class) {
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }

    static class MyThread extends Thread {

        public MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            System.out.println("[" + Thread.currentThread().getName() + "] await!");
            try {
                // 会阻塞在这里等待 mCountDownLatch 里的count变为0；
                // 也就是等待另外的WorkingThread调用countDown()
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LazySingleton lazySingleton = LazySingleton.getLazySingleton();
            System.out.println("[" + Thread.currentThread().getName() + "] end!");
        }
    }

    public static void main(String[] args) throws InterruptedException {

        MyThread thread1 = new MyThread("T1");
        MyThread thread2 = new MyThread("T2");
        MyThread thread3 = new MyThread("T3");
        MyThread thread4 = new MyThread("T4");
        MyThread thread5 = new MyThread("T5");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

        System.out.println("CountDownLatch Count Down，All Thread Await, Start After 500ms!");
        Thread.sleep(500);

        System.out.println("CountDownLatch Count Down，All Thread Started!");
        countDownLatch.countDown();
    }
}

